package rtda

import (
	"bytes"
	"encoding/binary"
	"math"
	"strings"

	"github.com/ethereum/go-ethereum/rlp"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda/heap"
	"hundsun.com/hsl/hschain/protos/ledger"
)

func (thread *Thread) StoreDataToStorage(field *heap.Field, val heap.Slot) []*ledger.NsKV {
	putKey := field.Class.Name + "." + field.Name
	var putBytes []byte
	var nsKvs []*ledger.NsKV
	switch field.Descriptor {
	case "Z", "B", "Ljava/lang/Byte;", "Ljava/lang/Boolean;":
		var putVal uint8
		if val.Ref != nil {
			putVal = uint8(val.Ref.Fields.([]heap.Slot)[0].Val)
		} else {
			putVal = uint8(val.Val)
		}
		putBytes := make([]byte, 1)
		putBytes[0] = putVal
		nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
		nsKvs = append(nsKvs, nsKv)
	case "C", "S", "Ljava/lang/Short;", "Ljava/lang/Character;":
		var putVal uint16
		if val.Ref != nil {
			putVal = uint16(val.Ref.Fields.([]heap.Slot)[0].Val)
		} else {
			putVal = uint16(val.Val)
		}
		putBytes := make([]byte, 2)
		putBytes[0] = byte(putVal)
		putBytes[1] = byte(putVal >> 8)
		nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
		nsKvs = append(nsKvs, nsKv)
	case "I", "J", "Ljava/lang/Integer;", "Ljava/lang/Long;":
		var putVal uint64
		if val.Ref != nil {
			putVal = uint64(val.Ref.Fields.([]heap.Slot)[0].Val)
		} else {
			putVal = uint64(val.Val)
		}
		bytesLen := 8
		var intV interface{}
		if field.Descriptor == "I" || field.Descriptor == "Ljava/lang/Integer;" {
			bytesLen = 4
			intV = int32(putVal)
		} else {
			intV = int64(putVal)
		}

		putBytes := make([]byte, bytesLen)
		var buffer bytes.Buffer
		binary.Write(&buffer, binary.BigEndian, intV)
		putBytes = buffer.Bytes()
		nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
		nsKvs = append(nsKvs, nsKv)
	case "F", "Ljava/lang/Float;":
		var putVal uint32
		if val.Ref != nil {
			putVal = uint32(val.Ref.Fields.([]heap.Slot)[0].Val)
		} else {
			putVal = uint32(val.Val)
		}
		putBytes := make([]byte, 4)
		binary.LittleEndian.PutUint32(putBytes, putVal)
		nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
		nsKvs = append(nsKvs, nsKv)
	case "D", "Ljava/lang/Double;":
		var putVal uint64
		if val.Ref != nil {
			putVal = uint64(val.Ref.Fields.([]heap.Slot)[0].Val)
		} else {
			putVal = uint64(val.Val)
		}
		putBytes := make([]byte, 8)
		binary.LittleEndian.PutUint64(putBytes, putVal)
		nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
		nsKvs = append(nsKvs, nsKv)
	case "Ljava/lang/String;":
		//basic type convert to bytes and write to storage
		if val.Ref != nil {
			putBytes = []byte(val.Ref.JSToGoStr())
			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[B", "[Ljava/lang/Byte;", "[V", "[Ljava/lang/Void;", "[Z", "[Ljava/lang/Boolean;":
		if val.Ref != nil {
			putBytes = val.Ref.GetGoBytes()
			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[C", "[Ljava/lang/Character;":
		if val.Ref != nil {
			putChars := val.Ref.GetChars()
			for i := 0; i < len(putChars); i++ {
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, putChars[i])
				putBytes = append(putBytes, buffer.Bytes()...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[S", "[Ljava/lang/Short;":
		if val.Ref != nil {
			putShorts := val.Ref.GetShorts()
			for i := 0; i < len(putShorts); i++ {
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, putShorts[i])
				putBytes = append(putBytes, buffer.Bytes()...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[I", "[Ljava/lang/Integer;":
		if val.Ref != nil {
			putInts := val.Ref.GetInts()
			for i := 0; i < len(putInts); i++ {
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, putInts[i])
				putBytes = append(putBytes, buffer.Bytes()...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[J", "[Ljava/lang/Long;":
		if val.Ref != nil {
			putLongs := val.Ref.GetLongs()
			for i := 0; i < len(putLongs); i++ {
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, putLongs[i])
				putBytes = append(putBytes, buffer.Bytes()...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[F", "[Ljava/lang/Float;":
		if val.Ref != nil {
			putFloats := val.Ref.GetFloats()
			for i := 0; i < len(putFloats); i++ {
				bits := math.Float32bits(putFloats[i])
				floatBytes := make([]byte, 4)
				binary.LittleEndian.PutUint32(floatBytes, bits)
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, floatBytes)
				putBytes = append(putBytes, floatBytes...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[D", "[Ljava/lang/Double;":
		if val.Ref != nil {
			putDoubles := val.Ref.GetDoubles()
			for i := 0; i < len(putDoubles); i++ {
				bits := math.Float64bits(putDoubles[i])
				doubleBytes := make([]byte, 8)
				binary.LittleEndian.PutUint64(doubleBytes, bits)
				var buffer bytes.Buffer
				binary.Write(&buffer, binary.BigEndian, doubleBytes)
				putBytes = append(putBytes, doubleBytes...)
			}

			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	case "[Ljava/lang/String;":
		if val.Ref != nil {
			putStrings := val.Ref.GetRefs()
			var allStrsBytes [][]byte
			for i := 0; i < len(putStrings); i++ {
				str := putStrings[i].JSToGoStr()
				strBytes, err := rlp.EncodeToBytes(str)
				if err != nil {
					panic("storeDataToStorage RLP encode string failed " + err.Error())
				}
				allStrsBytes = append(allStrsBytes, strBytes)
			}
			strPutBytes, err := rlp.EncodeToBytes(allStrsBytes)
			if err != nil {
				panic("storeDataToStorage RLP encode all strings failed " + err.Error())
			}
			putBytes = append(putBytes, strPutBytes...)
			nsKv := &ledger.NsKV{Namespace: "ClassField", Key: putKey, Value: putBytes}
			nsKvs = append(nsKvs, nsKv)
		}
	default:
		//暂时不支持比较复杂的引用类型数组
		if strings.HasPrefix(field.Descriptor, "[L") {
			panic("Not support referance array")
		}
		//referance type
		if val.Ref != nil {
			class := val.Ref.Class
			for _, f := range class.Fields {
				value := f.GetValue(val.Ref)
				fNsKvs := thread.StoreDataToStorage(f, value)
				nsKvs = append(nsKvs, fNsKvs...)
			}

		}
	}
	return nsKvs
}
